/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *	  http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.milib.animation.static_ {
	import com.googlecode.milib.base.MLBroadcastClass;
	import com.googlecode.milib.dbg.InstanceDebugServiceProxy;

	public class MLEase extends MLBroadcastClass {
		
		public static const type_none:String='none';		public static const type_Sin:String='Sin';
		public static const type_Log:String='Log';
		public static const type_Circ:String='Circ';
		public static const type_CircHard:String='CircHard';
		public static const type_Elastic:String='Elastic';
		public static const type_Bounce:String='Bounce';
		public static const type_Pow:String='Pow';
		public static const type_Bezier:String='Bezier';
		public static const type_Bezier2:String='Bezier2';
		
		public var event_Changed:String='Changed';
		
		private var easeFunction : Function;
		private var easeProps : Array;
		public var name :String;		private var isMirrorFilter:Boolean;
		private var isRevertFilter:Boolean;
		private static const INSTANCES_BY_NAME:Object={};

		public function MLEase(name:String=null) {
			easeProps=[];
			setupEaseSin();
			if(name && INSTANCES_BY_NAME[name]){
				throw new Error('There is already MLEase with name "'+name+'". Please use MLEase.getInstanceForName('+name+') static method instead');
			}
			INSTANCES_BY_NAME[name]=this;
			if(name && InstanceDebugServiceProxy.isDebuggerEnabled()){
				this.name=name;
				InstanceDebugServiceProxy.getMLDBGMain().regEase(this);
			}
		}//<>
		
		/*
		if(tb.filt.lustro or spec.lustro){
			//___LUSTRZANE ODBICIE
			return (1- func02mc[tb.type](1-_01,tb.param) )
			
		}else if(tb.filt.revert){
			//__REVERT
			if(_01<.02){return 0
			}else if(_01<=0.5){_01=(1- func02mc[tb.type](1-(_01*2),tb.param) )/2
			}else{_01=0.5+ func02mc[tb.type]((_01-0.5)*2,tb.param)*0.5}
			return _01
			
		}
		*/
		
		public function setIsMirrorFilter(mirrorFilter:Boolean):void {
			isMirrorFilter=mirrorFilter;
		}
		
		public function setIsRevertFilter(revertFilter:Boolean):void {
			isRevertFilter=revertFilter;
		}
		
		public function getIsMirrorFilter():Boolean {
			return isMirrorFilter;
		}
		
		public function getIsRevertFilter():Boolean {
			return isRevertFilter;
		}
		
		public function getEase(n:Number):Number {
			if(isMirrorFilter){
				return 1-Number(easeFunction.apply(this, [1-n].concat(easeProps)));
			}else if(isRevertFilter){
				if(n<.02){
					return 0;
				}else if(n<0.5){
					return ( 1-Number(easeFunction.apply(this, [1-n*2].concat(easeProps))) )/2;
				}else{
					return 0.5+Number(easeFunction.apply(this, [(n-0.5)*2].concat(easeProps)))*0.5;
				}
			}
			return Number(easeFunction.apply(this, [n].concat(easeProps)));
		}
		public function getEaseWithMirrorEase(n01:Number):Number {
			return 1-getEase(1-n01);
		}
		
		public function getType():String {
			switch(easeFunction){
				case none: return type_none; break;
				case Log: return type_Log; break;
				case sin: return type_Sin; break;
				case circ: return type_Circ; break;
				case circHard: return type_CircHard; break;
				case pow: return type_Pow; break;
				case bezier: return type_Bezier; break;
				case bezier2: return type_Bezier2; break;
				case bounce: return type_Bounce; break;
				case elastic: return type_Elastic; break;
				default:
					throw new Error('Undefined ease type');
			}
		}
		
		public function setupEaseNone():MLEase { return setupEaseFunctionAndAruments(none); }
		public function setupEaseLog():MLEase { return setupEaseFunctionAndAruments(Log); }
		public function setupEaseSin():MLEase { return setupEaseFunctionAndAruments(sin); }
		public function setupEaseCirc():MLEase { return setupEaseFunctionAndAruments(circ); }
		public function setupEaseCircHard():MLEase { return setupEaseFunctionAndAruments(circHard); }
		
		public function setupEasePow(num:Number):MLEase {
			return setupEaseFunctionAndAruments(pow, [num]);
		}
		public function setupEaseBezier(n0:Number, n1:Number):MLEase {
			return setupEaseFunctionAndAruments(bezier, [n0, n1]);
		}
		public function setupEaseBezier2(n0:Number, n05:Number, n1:Number):MLEase {
			return setupEaseFunctionAndAruments(bezier2, [n0, n05, n1]);
		}
		public function setupEaseBounce(humps:Number, power:Number, friction:Number):MLEase {
			return setupEaseFunctionAndAruments(bounce, [humps, power, friction]);
		}
		public function setupEaseElastic(humps:Number, dynamic_:Number, power:Number, friction:Number):MLEase {
			return setupEaseFunctionAndAruments(elastic, [humps, dynamic_, power, friction]);
		}
		private function setupEaseFunctionAndAruments(func:Function, args:Array=null):MLEase {
			easeFunction=func;
			easeProps= args==null ? [] : args;
			broadcastEvent(event_Changed);
			return this;
		}
		
//		public function getEaseName():String {
//			var name:String=MILibUtil.getMethodName(this, easeFunction);
//			return name.substr(0, 1).toUpperCase()+name.substr(1);
//		}
		
		public function getEaseProps():Array {
			return easeProps;
		}
		
		public function setEaseProps(props:Array):void {
			easeProps=props;
		}
		
		public function setupEaseProp(value:Number, propIndex:Number):void {
			easeProps[propIndex]=value;
			broadcastEvent(event_Changed);
		}
		
		private function none(n:Number):Number { return n; }
		
		private function Log(n:Number):Number {return -Math.log(1-n)*.2; }
		
		private function sin(n:Number):Number {return Math.sin(n*1.57); }
		
		private function pow(n:Number, num:Number):Number {
			num=Math.pow(n, num);
			if(num<0.0000108){return 0;}
			return num;
		}
		
		private function circ(n:Number):Number {
			return -(Math.sqrt(1 - n*n) - 1);
		}
		
		private function circHard(n:Number):Number {
			var c:Number=circ(n);
			c-=(c*.8)*(1-c);
			return c;
		}
		
		private function bezier(n:Number, n0:Number, n1:Number):Number {
			return 1-bezierALG(n, 0, n0, n1, 1);
		}
		
		private function bezier2(n:Number, n0:Number, n05:Number, n1:Number):Number {
			return 1-bezier2ALG(n, 0, n0, n05, n1, 1);
		}
		
		//ob-->humps:1|40, power:0|3, friction:0|3
		private function bounce(n:Number, humps:Number, power:Number, friction:Number):Number {
			var dzi01:Number=1/(int(humps)+.5);
			var gln:Number=(-Math.log(1-n)*.2)/dzi01;
			var gln2:Number=gln*2;
			var part4:Number=(gln2+6)%4;
			var part4P:Number=part4-int(part4);
			if(gln2<1){
				return 1+Math.sin(-(1-part4P)*1.57);
			}else{
				var skokSpow:Number=spow(power, gln/friction);
				if(part4>=0 && part4<1){//_______0
					return 1-Math.sin((1-part4P)*1.57)*skokSpow;
				}else if(part4>=1 && part4<2){//_1
					return 1+Math.sin(-part4P*1.57)*skokSpow;
				}else if(part4>=2 && part4<3){//_2
					return 1+Math.sin(-(1-part4P)*1.57)*skokSpow;
				}else if(part4>=3 && part4<4){//_3
					return 1-Math.sin(part4P*1.57)*skokSpow;
				}
			}
			return 0;
		}
		
		//humps:1|40, dynamic_:0|5, power:0|3, friction:0|3
		private function elastic(n:Number, humps:Number, dynamic_:Number, power:Number, friction:Number):Number {
			var dzi01:Number=1/(int(humps)+.5);
			var gln:Number=Math.pow(n, dynamic_)/dzi01;
			var glp:Number=gln-int(gln);
			var gln2:Number=gln*2;
			var part4:Number=(gln2+6)%4;
			var part4P:Number=part4-int(part4);
			var skokSpow:Number=spow(power, gln/friction);
			if(gln<1){
				return Math.sin(glp*1.57)*(1+skokSpow);
			}else{
				if(part4>=0 && part4<1){//_______0
					return 1+Math.sin((1-part4P)*1.57)*skokSpow;
				}else if(part4>=1 && part4<2){//_1
					return 1+Math.sin(-part4P*1.57)*skokSpow;
				}else if(part4>=2 && part4<3){//_2
					return 1+Math.sin(-(1-part4P)*1.57)*skokSpow;
				}else if(part4>=3 && part4<4){//_3
					return 1+Math.sin(part4P*1.57)*skokSpow;
				}
			}
			return 0;
		}
		
		private function bezierALG1(t:Number):Number { return t*t*t; }
		private function bezierALG2(t:Number):Number { return 3*t*t*(1-t); }
		private function bezierALG3(t:Number):Number { return 3*t*(1-t)*(1-t); }
		private function bezierALG4(t:Number):Number { t=1-t;return t*t*t; }
		private function bezierALG(cou:Number, n1:Number, n2:Number, n3:Number, n4:Number):Number {
			return	n1*bezierALG1(cou)+ n2*bezierALG2(cou)+ n3*bezierALG3(cou)+ n4*bezierALG4(cou);
		}
		
		private function bezier2ALG1(t:Number):Number { return t*t*t*t; }
		private function bezier2ALG2(t:Number):Number { return 4*t*t*t*(1-t); }
		private function bezier2ALG3(t:Number):Number { return 6*t*t*(1-t)*(1-t); }
		private function bezier2ALG4(t:Number):Number { return 4*t*(t-=1)*t*t; }
		private function bezier2ALG5(t:Number):Number { t=1-t;return t*t*t*t; }
		private function bezier2ALG(cou:Number, n1:Number, n2:Number, n3:Number, n4:Number, n5:Number):Number {
			return n1*bezier2ALG1(cou)+ n2*bezier2ALG2(cou)+ n3*bezier2ALG3(cou)+ n4*bezier2ALG4(cou)+ n5*bezier2ALG5(cou);
		}
		
		private function spow(n:Number, spow:Number):Number {
			return n/Math.pow(2,spow-1);
		}
		
		public static function getInstanceForName(name:String):MLEase {
			if(!INSTANCES_BY_NAME[name]){
				new MLEase(name);
			}
			return INSTANCES_BY_NAME[name];
		}
		
//		public function getDebugContents():Array {
//			return super.getDebugContents().concat([
//				_global.pl.milib.anim.dbg.DBGWindowMIEaseContent.forInstance(this)
//			]);
//		}
		
	}		
}